Skip to content

Story 5.3: the identity engine gets its own abstention cause — and the two mutations the story did not ask for - #48

Merged
guycorbaz merged 3 commits into
masterfrom
story-5.3-engine-abstention-cause
Jul 29, 2026
Merged

Story 5.3: the identity engine gets its own abstention cause — and the two mutations the story did not ask for#48
guycorbaz merged 3 commits into
masterfrom
story-5.3-engine-abstention-cause

Conversation

@guycorbaz

Copy link
Copy Markdown
Owner

What lands

Outcome::Abstained carries IdentityAbstentionCause (new crates/opencmdb-core/src/identity/cascade.rs): two variants, Ambiguous and AbsenceOfProof, each traced to a row of D13's six-row table. Four tests pin its claims.

This is a TYPE and its tests. It writes no engine — no cascade, no rule, no Decision, no join, no producer. Those are stories 5.4/5.5/5.6.

Expectation::MustAbstain keeps gap::AbstentionCause and reconcile's enum is byte-unchanged, so no committed artefact was re-hashed: git status fixtures/ and fixtures/MANIFEST.toml both verified empty.

Numbers, re-measured on this tree

263 → 267 tests (135 bin + 90 core + 42 xtask). cargo xtask ci all green (ℹ views-hash STALE, exit 0 — expected, deliberately not regenerated). Both clippy forms green, including the CI one without --all-targets — the only invocation that catches an import kept alive solely by a test module, which is exactly what the two asymmetric import edits here risked.

Seven mutations, not the five specified

The two additions are the point: all()'s doc asserts two mechanisms and the five specified mutations could measure only one.

# mutation observed
M1 delete Ambiguous 6 × E0599 at 4 sites
M1b add a third variant 1 × E0004, the witness match
M1c …also widen the literal E0308, "expected [IdentityAbstentionCause; 2] because of return type"
M2 (must-abstain, Abstained) → Fail 8 reds (2 bin + 6 core), 2 of them new
M3 (must-not-merge, Abstained) → Fail 5 reds, exactly the predicted set
M4 add Ambiguous to gap::AbstentionCause exactly one E0004 at page.rs:114:11
M5 revert the field type 5 × E0308, 3 of them the new guards; lib still compiles

Two prediction/observation differences, recorded rather than smoothed

  • M1 does not behave as the story predicted. Deleting a variant gives E0599, never the predicted E0308, and trap_gate.rs's four retyped sites never red at all — opencmdb-core fails to compile first, so opencmdb-bin is never compiled.
  • M2 reds eight tests, not seven. The story missed scoring_ignores_the_rule_because_the_trap_runner_owns_it, whose last assertion is the flipped cell.

M4 was measured on both sides: under the mutation, core still reports 90 passed and xtask 42. So widening the reconciliation enum costs a user-facing label plus two locale strings for a variant reconcile can never produce — the argument for a separate type, measured instead of asserted.

One count corrected by this commit

Outcome::Abstained now has nine construction sites, not the six the story's Dev Notes measured at 12be2fc — this story's own test loops added three. All nine are still in test modules, which M5 measured independently by breaking only the lib test target.

Register

The story-4.6a bullet is CLOSED by append-and-strike, naming the branch taken and the tests that hold it — and correcting, in the closure note, its own sentence claiming a different outcome-side type "would make comparison asymmetric". There is no comparison to go asymmetric. The two NoMatch bullets are annotated ↺ PARTLY closed, not struck: the vocabulary exists, the mapping has no producer until 5.4/5.5. A new ## Deferred from: story-5.3 section opens five items, each with a named owner.

🤖 Generated with Claude Code

guycorbaz and others added 3 commits July 28, 2026 20:45
…o mutations the story did not ask for

`Outcome::Abstained` carries `IdentityAbstentionCause` (new
`opencmdb-core/src/identity/cascade.rs`): two variants, `Ambiguous` and
`AbsenceOfProof`, each traced to a row of D13's six-row table. Four tests pin
its claims. `Expectation::MustAbstain` keeps `gap::AbstentionCause` and
`reconcile`'s enum is byte-unchanged, so no committed artefact was re-hashed —
`git status fixtures/` and `MANIFEST.toml` both verified empty.

This is a TYPE and its tests. There is no cascade, no rule, no `Decision`, no
join and no producer; those are stories 5.4/5.5/5.6.

263 → 267 tests (135 bin + 90 core + 42 xtask). Both clippy forms green,
including the CI one without `--all-targets` — the only invocation that catches
an import kept alive solely by a test module, which is exactly what the two
asymmetric import edits here risked.

SEVEN mutations run, not the five specified, and the two additions are the
point: `all()`'s doc asserts two mechanisms and the five could measure only one.
M1b (a third variant → `error[E0004]` at the witness match) and M1c (widening
the literal → `error[E0308]`, rustc printing "expected
`[IdentityAbstentionCause; 2]` because of return type") measure both, rather
than shipping half a doc sentence as a prediction.

Two prediction/observation differences are recorded rather than smoothed:

  · M1 does NOT behave as predicted. Deleting a variant gives `error[E0599]`,
    never the predicted `E0308`, and `trap_gate.rs`'s four retyped sites never
    red at all — `opencmdb-core` fails to compile first, so `opencmdb-bin` is
    never compiled.
  · M2 reds EIGHT tests, not the seven predicted. The story missed
    `scoring_ignores_the_rule_because_the_trap_runner_owns_it`, whose last
    assertion IS the flipped cell. M3's five-red set matched exactly.

M4 is AC4's evidence and was measured on both sides: adding `Ambiguous` to
`gap::AbstentionCause` gives exactly ONE `error[E0004]`, at `page.rs:114:11`,
and under that mutation core still reports 90 passed and xtask 42. Widening the
reconciliation enum therefore costs a user-facing label plus two locale strings
for a variant `reconcile` can never produce — the argument for a separate type,
now measured instead of asserted.

One count in the story's own Dev Notes is falsified by this commit and is
corrected rather than left standing: `Outcome::Abstained` has NINE construction
sites, not six. All nine are still in test modules — M5 measured that
independently, breaking only the `lib test` target.

Register: the story-4.6a bullet is CLOSED by append-and-strike, naming the
branch taken and the tests that hold it, and correcting in the closure note its
own sentence claiming a different outcome-side type "would make comparison
asymmetric" — there is no comparison to go asymmetric. The two `NoMatch`
bullets are annotated PARTLY closed, not struck: the vocabulary exists, the
mapping has no producer until 5.4/5.5. A new `## Deferred from: story-5.3`
section opens five items, each with a named owner.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Task 10 is checked with the honest state written beside it: branch → PR #48 →
CI green in 1m00s (run 30389174367). The squash merge is NOT done and is not
dev-story's to do — code-review comes between here and the merge, and `done` is
the MERGE's business (5.1 established this; 5.2 and 5.2b confirmed it).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e story turns out to have a green exit

Three parallel layers. NO behavioural defect was found by any of them; the
Acceptance Auditor reproduced SIX of the seven mutations and not one observed
red set differed from the record. What fell is documentation truth, in a story
built to guard exactly that.

THE DECISION. `all()`'s witness stops the build on a new variant (error[E0004])
but does NOT force the variant into the list. Measured by RUNNING the lazy
repair: a bare `Self::NewThing => {}` arm leaves `all()` returning 2 of 3 while
the suite reports 90 passed. Guy's call — keep the mechanism, state its limit
truthfully, register the residue with owner 5.14. Two closures were built and
measured first and both rejected:

  · an `ordinal()`/`slot()` witness is GREEN with the variant missing (91
    passed) — it closes nothing, because `slot()` is never called on a variant
    that is not already in `all()`. This was the reviewer's own first
    recommendation, withdrawn on measurement rather than on argument;
  · a single-source `macro_rules!` works (`all().len() = 3`) but violates AC1's
    literal `[Self; 2]` return type, and makes adding a variant frictionless —
    the opposite of AC1b, which calls a third variant a FINDING.

`all()`'s doc now states the guarantee and its limit, and the witness carries a
comment naming the wrong repair for whoever arrives on the E0004.

ALL THREE LAYERS CONVERGED on a false row count in the enum's own guard
paragraph — "the four D13 rows that produce none" when it is two. Inherited from
an AC1b that contradicts itself four lines apart; the dev copied the wrong half
instead of raising it as a finding.

Also patched: `all()`'s doc claiming a new variant breaks it "twice" when the two
errors are alternatives along one repair path; the variant-set test's doc
asserting a guard it does not have; a re-count the story pre-named and skipped
(`Expectation::MustAbstain` 7 → 8, the eighth being this story's own test);
AC1's `Given` clause, refuted by the story's own M4 and traced back to the 4.6a
register bullet, now struck there beside a SECOND false clause that bullet's
first closure had announced but never struck; the cross-vocabulary test widened
from 2 of 6 pairs to all six and RE-PROVEN red under M2; and eight smaller
count, antecedent and placement defects across four documents.

CLAUDE.md's Epic 5 line, stale since 5.2 and 5.2b merged, is fixed here rather
than deferred — the drift was inherited, the occasion is this push.

REVIEWER'S OWN DEFECT, recorded rather than quietly fixed: two layers with write
access ran against ONE working tree, and the Edge Case Hunter observed another
agent's mutation mid-pass. Nothing was corrupted — tree verified clean, md5
stable, gate re-run whole — but the next multi-layer review gives each writing
layer its own git worktree.

Test count UNCHANGED at 267 (135 + 90 + 42): the widened test is still one test,
so the number says nothing about these patches. The re-run red set does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@guycorbaz
guycorbaz merged commit 62f9c83 into master Jul 29, 2026
1 check passed
@guycorbaz
guycorbaz deleted the story-5.3-engine-abstention-cause branch July 29, 2026 07:30
guycorbaz added a commit that referenced this pull request Jul 29, 2026
Bookkeeping for story 5.3, after PR #48 was squash-merged as `62f9c83`.

- Status → `done` in the story file and in `sprint-status.yaml`.
Separate commit on purpose: **`done` is the MERGE's business** in this
project's flow, which is why the `code-review` workflow's own default of
setting it at the end of the review was not applied — same shape as 5.1,
5.2 and 5.2b.
- `docs/project-context.md` — Epic 5's line says 5.3 is done and 5.4 is
next; the test count reads **267 on master** (135 + 90 + 42) instead of
a "with 5.3 in review" figure the merge made stale.
- `CLAUDE.md` — the Epic 5 sentence now names what 5.3 shipped: the
identity engine's own abstention vocabulary in the new
`crates/opencmdb-core/src/identity/`, and **no engine**.

No code changes. `cargo xtask ci` green.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant